package algebra.visitors;

import index.DescriptionIndex;

import java.util.LinkedList;
import java.util.List;

import datatableaux.Reason;

import algebra.OrderingDescription;
import algebra.op.CQuery;
import algebra.op.InformativeObjectQuery;
import algebra.op.IntersectionQuery;
import algebra.op.PQuery;
import algebra.op.ProjectionQuery;
import algebra.op.Query;
import algebra.op.ScanQuery;
import algebra.op.SelectionQuery;

import utils.Util;
import utils.exception.OrderingDescriptionException;

import kb.DescriptionLogicsKB;
import entities.ConceptAssertion;
import entities.Individual;
import entities.StarConceptAssertion;
import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.vocabulary.KBVocabulary;
import entities.vocabulary.QueryKeywords;

/**
 * The evaluation of a query returns a set of Concept Assertions, i.e., in the form of a:C.
 * @author j55wu
 *
 */
public class QueryVisitorEvaluator {
	private DescriptionLogicsKB kb;
	//private ABox ab;
	private OrderingDescription od;
	
	public QueryVisitorEvaluator(DescriptionLogicsKB kb){
		try {
			this.od = new OrderingDescription ("un",kb);
		} catch (OrderingDescriptionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.kb = kb;
	}
	public QueryVisitorEvaluator(DescriptionLogicsKB kb, OrderingDescription od){
		this.kb = kb;
		this.od = od;
		//this.tb = kb.getTbox();
	}

//	public QueryVisitorEvaluator(TBox tb, ABox ab) {
//		super();
//		this.tb = tb;
//		this.ab = ab;
//		this.od = null;
//	}

	
	

//	public QueryVisitorEvaluator(TBox tb) {
//		super();
//		this.tb = tb;
//		this.ab = null;
//		this.od = null;
//	}

	/**
	 * A query is special iff the evaluation of the query returns {*:C}.
	 * @param q
	 * @return
	 */
	public static boolean isSpecialAssertion(List<ConceptAssertion> res){
		if(res.size() != 1){
			return false;
		}
		return (res.get(0) instanceof StarConceptAssertion);
//		for(ConceptAssertion ca: res){
//			if(ca instanceof StarConceptAssertion){
//				return true;
//			}
//		}
//		return false;
	}


	public List<ConceptAssertion> visit(Query q){
		if(q instanceof PQuery){
			return EvaluatePQuery((PQuery) q, kb);
		}else if(q instanceof CQuery){
			return EvaluateConcpetQuery((CQuery) q);
		}else if(q instanceof ScanQuery){
			return EvaluateScanQuery((ScanQuery) q, kb, od);
		}else if(q instanceof IntersectionQuery){
			return EvaluateIntersectionQuery((IntersectionQuery) q);
		}else if(q instanceof InformativeObjectQuery){
			InformativeObjectQuery iq = (InformativeObjectQuery)q;
			try {
				return visit(iq.translate());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else if(q instanceof ProjectionQuery){
			return EvaluateProjectionQuery((ProjectionQuery) q, kb);
		}else if(q instanceof SelectionQuery){
			return EvaluateSelectionQuery((SelectionQuery) q, kb, od);
		}{
			System.err.println("This type of queries cannot be evaluated: "+ q.getClass());
			return null;
		}
		
	}
	
	private List<ConceptAssertion> EvaluatePQuery(PQuery q, DescriptionLogicsKB kb) {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
		for(Individual ind:kb.getAbox().getABoxIndividuals()){
			res.add(new ConceptAssertion(ind, KBVocabulary.CONCEPT_TOP));
		}		
		return res;
	}



	public ConceptAssertion findIndividualInQueryResults(Individual ind, List<ConceptAssertion> cache){
		for(ConceptAssertion ca:cache){
			if(ca.getIndividual().equals(ind)){
				return ca;
			}
		}
		return null;
	}
	


	private List<ConceptAssertion> EvaluateConcpetQuery(CQuery cq){
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
		Concept c = cq.getQueryCocnept();
		res.add(new StarConceptAssertion(c));
		return res;
	}
	
	//the returned results NEVER have star assertions.
	private List<ConceptAssertion> EvaluateScanQuery(ScanQuery cq, DescriptionLogicsKB kb, OrderingDescription od){
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
		if(od == null){
			//no ordering required, default to Un
			try {
				//note that no KB reasoning required!
				od = new OrderingDescription(QueryKeywords.ORDERING_UN, DescriptionLogicsKB.newNullKB());
			} catch (OrderingDescriptionException e) {
				e.printStackTrace();
			}
		}
		//no KB reasoning required for cached results
		if(cq.getCache() == null){
			//no default cache, build a:T from KB
			ScanQueryVisitorCacheSelector visitor = new ScanQueryVisitorCacheSelector(kb, od);
			cq.acceptVisitor(visitor);
		}
		DescriptionIndex idx = indexingQueryResults(cq.getCache(), od, DescriptionLogicsKB.newNullKB());	
		try {
			List<ConceptAssertion> qs = visit(cq.getSubQuery());
			if(isSpecialAssertion(qs)){
				//star semantics
				Concept D = ((StarConceptAssertion) qs.get(0)).getConcept();
				for(ConceptAssertion ac:cq.getCache()){
					ConceptAssertion ad = new ConceptAssertion(ac.getIndividual(), D); 
					Reason myreasoner = new Reason(false);
					if(myreasoner.ABoxUnionAssertionEntailAssertionCheck(ac, ad, kb)){
						res.add(ac);
					}
				}
			}else{
				//standard semantics
				for(ConceptAssertion q:qs){
					res.addAll(idx.search(q));
				}
			}
		} catch (OrderingDescriptionException e) {
			e.printStackTrace();
		}
//		for(ConceptAssertion ca:cq.getCachedResults()){
//			Individual a = ca.getIndividual();
//			List<ConceptAssertion> subQueryRes = EvaluateQuery(cq.getSubQuery(), tb, ab, null);
//			ConceptAssertion da = findIndividualInQueryResults(a, subQueryRes);
//			if(da != null){
//				//check if ca |= da, if so, ca qualifies as the result.
//				Reason reasoner = new Reason(false);
//				boolean ans = reasoner.ABoxConsistentWRTTBox(true, null, reasoner.processAB(ca), da);
//				if(ans){
//					res.add(ca);
//				}
//			}
//		}
		return res;
	}
	
	//see semantics: a: (C1 and C2), nested loop join 
	private List<ConceptAssertion> EvaluateIntersectionQuery(IntersectionQuery iq){
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
		List<ConceptAssertion> q1 = visit(iq.getFirstSubQuery());
		List<ConceptAssertion> q2 = visit(iq.getSecondSubQuery());
		boolean bq1 = isSpecialAssertion(q1);
		boolean bq2 = isSpecialAssertion(q2);
		if( bq1 && bq2){
			//both are star assertions
			Concept d1 = q1.get(0).getConcept();
			Concept d2 = q2.get(0).getConcept();
			res.add(new StarConceptAssertion(new AndConcept(d1, d2)));
		}else if(bq1 && !bq2){
			Concept con = q1.get(0).getConcept();
			for(ConceptAssertion ca:q2){
				res.add(new ConceptAssertion(ca.getIndividual(), 
						new AndConcept(ca.getConcept(), con)));
			}
		}else if(!bq1 && bq2){
			Concept con = q2.get(0).getConcept();
			for(ConceptAssertion ca:q1){
				res.add(new ConceptAssertion(ca.getIndividual(), 
						new AndConcept(ca.getConcept(), con)));
			}
		}else{
			//normal concept assertions in both sub-queries
			for(ConceptAssertion ca:q1){
				for(ConceptAssertion da:q2){
					if(da.getIndividual().equals(ca.getIndividual())){
						res.add(new ConceptAssertion(ca.getIndividual(), 
								new AndConcept(ca.getConcept(), da.getConcept())));
						//an individual only appears once in the ABox
						break;
					}
				}
			}
			
		}
		return res;
	}
	
//	private List<ConceptAssertion> EvaluateInformativeQuery(ProjectionQuery pq, TBox tb, 
//			ABox ab){
//		List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
//		
//		return res;
//	}
	
	/*
	 * If the subquery is *:C?
	 */
	private List<ConceptAssertion> EvaluateProjectionQuery(ProjectionQuery pq, DescriptionLogicsKB kb){
		//List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
		ProjectionQueryVisitorEvaluator eval = new ProjectionQueryVisitorEvaluator(kb, null);
		return pq.acceptVisitor(eval);
	}
	
	
	private List<ConceptAssertion> EvaluateSelectionQuery(SelectionQuery q, DescriptionLogicsKB kb, 
			OrderingDescription od) {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion> ();
		if(od == null){
			//no ordering required, default to id
			try {
				od = new OrderingDescription(QueryKeywords.ORDERING_UN, kb);
			} catch (OrderingDescriptionException e) {
				e.printStackTrace();
			}
		}
		List<ConceptAssertion> pos = visit(q.getSubQuery());
//		System.out.println("sub:"+q.getSelectionCondition()+", size:"+pos.size());
		if(isSpecialAssertion(pos)){
			//star semantics
			StarConceptAssertion sca = (StarConceptAssertion) pos.get(0);
			if(Util.subsumes(sca.getConcept(), q.getSelectionCondition(), kb)){
				// test if K |= D under C. if yes, populate the results.
				if(kb.getAbox() != null){
					return kb.getAbox().populateStarConceptAssertion(sca);
				}else{
					return new LinkedList<ConceptAssertion> ();
				}
			}else{
				//populate assertions for testing
				if(kb.getAbox() != null){
					pos = kb.getAbox().populateStarConceptAssertion(sca);
				}
			}
		}
		//build an index tree for subquery results, and search among it the nodes a:D s.t.
		// K U {a:D} |= a:C?
		DescriptionIndex idx = indexingQueryResults(
				pos, od, kb);	
		try {
			for(ConceptAssertion cq:pos){
				res.addAll(
					idx.search(new ConceptAssertion(cq.getIndividual(), q.getSelectionCondition()))
						);
			}
		} catch (OrderingDescriptionException e) {
			e.printStackTrace();
		}
		return res;
	}

	
	/**
	 * Indexing the query results wrt the given ordering description, a tbox and an abox.
	 * @param res
	 * @param od
	 * @return
	 */
	public static DescriptionIndex indexingQueryResults(List<ConceptAssertion> res, OrderingDescription od, 
			DescriptionLogicsKB kb){
		DescriptionIndex idx = new DescriptionIndex(null, od, kb);
		try {
			idx.load(res);
		} catch (OrderingDescriptionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return idx;
	}
	
	
}
